﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using TextToHTMLExtensionInterfaces;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

namespace TextToHTML
{
    /// <summary>
    /// Tools to help with rendering the HTML
    /// </summary>
    class RenderHTMLTools
    {
        /// <summary>
        /// Null rendering
        /// </summary>
        public RenderHTMLTools()
        {
        }

        /// <summary>
        /// Renders with a container composition first.
        /// </summary>
        /// <param name="cont"></param>
        public RenderHTMLTools(CompositionContainer cont)
        {
            CompositionBatch batch = new CompositionBatch();
            batch.AddPart(this);
            cont.Compose(batch);
        }

        /// <summary>
        /// Given some text as input, return it as html
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        public FileInfo RenderText(string text)
        {
            ///
            /// Create a HTML temp file
            /// 

            FileInfo output = new FileInfo(Path.ChangeExtension(Path.GetTempFileName(), ".html"));
            using (TextWriter tr = output.CreateText())
            {
                tr.WriteLine("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">");
                tr.WriteLine("<html xmlns=\"http://www.w3.org/1999/xhtml\">");
                tr.WriteLine("<head><title>Translated Text</title></head>");
                tr.WriteLine("<body>");

                RenderTextToStream(tr, text);

                tr.WriteLine("</body>");
                tr.Close();
            }

            return output;
        }

        /// <summary>
        /// Given some text, write it out to the stream. Nice way of avoiding dealing with
        /// all the messy (!!) html header/footer stuff.
        /// </summary>
        /// <param name="tr"></param>
        /// <param name="text"></param>
        private void RenderTextToStream(TextWriter tr, string text)
        {
            using (StringReader textReader = new StringReader(text))
            {
                string line = textReader.ReadLine();
                bool isfirst = true;
                while (line != null)
                {
                    if (!isfirst)
                    {
                        tr.WriteLine();
                    }
                    isfirst = false;

                    tr.Write("<p>");
                    tr.Write(RenderTextLineToString(line));
                    tr.Write("</p>");

                    line = textReader.ReadLine();
                }
            }
        }

        [Import]
        private ExportCollection<IParseText> Parsers {get; set; }

        [Import]
        private ExportCollection<IRenderToHTML> Renders { get; set; }

        /// <summary>
        /// Translates a single line of text - looking for replacements and substitutions.
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
        private string RenderTextLineToString(string line)
        {
            ///
            /// Just bail if things look bad
            /// 

            if (Parsers == null)
            {
                return line;
            }

            List<TextRun> matchedText = new List<TextRun>();
            foreach (var p in Parsers)
            {
                TextRun[] runs = p.GetExportedObject().FindTextToReplace(line);
                foreach (var r in runs)
                {
                    if ((from oldr in matchedText where Overlaps(oldr, r) select oldr).Count() == 0) {
                        matchedText.Add(r);
                    }
                }
            }

            ///
            /// Now, we can attempt to translate each guy into some new text. Sort in reverse order so
            /// we don't mess up the indicies as we go.
            /// 

            var sortedRuns = from r in matchedText
                             select r;

            foreach (TextRun r in matchedText)
            {
                var possibleRenders = from rend in Renders
                                      where rend.Metadata.Keys.Contains("type") && rend.Metadata["type"] == r.whatItIs.GetType() && rend.GetExportedObject().CanRender(r.whatItIs)
                                      select rend.GetExportedObject();

                ///
                /// For now we just take the top one!
                /// 

                IRenderToHTML render = possibleRenders.FirstOrDefault();
                if (render != null)
                {
                    line = line.Substring(0, r.Start)
                        + render.Render(r.whatItIs)
                        + line.Substring(r.Start + r.Length);
                }

            }

            return line;
        }

        /// <summary>
        /// Returns true if the two text runs overlap
        /// </summary>
        /// <param name="oldr"></param>
        /// <param name="r"></param>
        /// <returns></returns>
        private bool Overlaps(TextRun oldr, TextRun r)
        {
            int oldEnd = oldr.Start + oldr.Length;
            int rEnd = r.Start + r.Length;

            if (rEnd < oldr.Start)
                return false;
            if (oldEnd < r.Start)
                return false;

            if (r.Start > oldEnd)
                return false;
            if (oldr.Start > rEnd)
                return false;

            return true;
        }
    }
}
